//==================================================================
//  Copyright (C) 2006-2007  Davide Pasca
//
//  This library is free software; you can redistribute it and/or
//  modify it under the terms of the GNU Lesser General Public
//  License as published by the Free Software Foundation; either
//  version 2.1 of the License, or (at your option) any later version.
//
//  This library is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//  Lesser General Public License for more details.
//
//  You should have received a copy of the GNU Lesser General Public
//  License along with this library; if not, write to the Free Software
//  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
//==================================================================
/// @file image_base.h
///
///
/// @author Davide Pasca
///
//==================================================================

#include "image_base.h"

using namespace PUtils;

//==================================================================
#pragma pack(push,1)

typedef unsigned int	myDWORD;
typedef unsigned short	myWORD;
typedef	unsigned int	myLONG;

typedef struct {
	myDWORD      biSize;
	myLONG       biWidth;
	myLONG       biHeight;
	myWORD       biPlanes;
	myWORD       biBitCount;
	myDWORD      biCompression;
	myDWORD      biSizeImage;
	myLONG       biXPelsPerMeter;
	myLONG       biYPelsPerMeter;
	myDWORD      biClrUsed;
	myDWORD      biClrImportant;
} myBITMAPINFOHEADER;

typedef struct  { // bmfh 
	myWORD    bfType; 
	myDWORD   bfSize; 
	myWORD    bfReserved1; 
	myWORD    bfReserved2; 
	myDWORD   bfOffBits; 
} myBITMAPFILEHEADER;

typedef struct  { // rgbq 
	u_char	rgbBlue; 
	u_char	rgbGreen; 
	u_char	rgbRed; 
	u_char	rgbReserved; 
} myRGBQUAD;

#pragma pack(pop)

//==================================================================
static int _channel_format_sizes[] =
{
	1,	// U8,
	2,	// U16,
	4,	// U32,
	4,	// F32,
};

//==================================================================
// [DES][SRC]
ImageBase::Channel::ConvertCallback	ImageBase::Channel::_conv_table[FORMAT_N][FORMAT_N] =
{
	// FORMAT_U8,	FORMAT_U16,		FORMAT_U32,		FORMAT_F32,  (SRC)

	Assign_U8_U8,	Assign_U8_U16,	Assign_U8_U32,	Assign_U8_F32,		// FORMAT_U8,  (DES)
	Assign_U16_U8,	Assign_U16_U16,	Assign_U16_U32,	Assign_U16_F32,		// FORMAT_U16, (DES)
	Assign_U32_U8,	Assign_U32_U16,	Assign_U32_U32,	Assign_U32_F32,		// FORMAT_U32, (DES)
	Assign_F32_U8,	Assign_F32_U16,	Assign_F32_U32,	Assign_F32_F32,		// FORMAT_F32, (DES)
};

//==================================================================
ImageBase::Channel::~Channel()
{
	freeChannel();
}

//==================================================================
void ImageBase::Channel::freeChannel()
{
	if ( _owns_data )
		SAFE_FREE( _datap );

	_owns_data = false;
}

//==================================================================
void PUtils::ImageBase::Channel::SetFormat( Format format )
{
	_format = format;
	switch ( format )
	{
	case FORMAT_U8:		GetAsF32 = GetAsF32_U8;		SetAsF32 = SetAsF32_U8;		break;
	case FORMAT_U16:	GetAsF32 = GetAsF32_U16;	SetAsF32 = SetAsF32_U16;	break;
	case FORMAT_U32:	GetAsF32 = GetAsF32_U32;	SetAsF32 = SetAsF32_U32;	break;
	case FORMAT_F32:	GetAsF32 = GetAsF32_F32;	SetAsF32 = SetAsF32_F32;	break;
	default:
		PASSERT( 0 );
		break;
	}
}

//==================================================================
void ImageBase::setBaseFormat( BaseFormat base_format )
{
	Channel	*chp = _channels;

	switch ( base_format )
	{
	case BF_INDEX8:
		chp->_type = Channel::TYPE_INDEX;		chp->SetFormat( Channel::FORMAT_U8 ); ++chp;
		break;
	case BF_A8:
		chp->_type = Channel::TYPE_ALPHA;		chp->SetFormat( Channel::FORMAT_U8 ); ++chp;
		break;
	case BF_L8:
		chp->_type = Channel::TYPE_LUMINANCE;	chp->SetFormat( Channel::FORMAT_U8 ); ++chp;
		break;
	case BF_A8L8:
		chp->_type = Channel::TYPE_ALPHA;		chp->SetFormat( Channel::FORMAT_U8 ); ++chp;
		chp->_type = Channel::TYPE_LUMINANCE;	chp->SetFormat( Channel::FORMAT_U8 ); ++chp;
		break;
	case BF_L8A8:
		chp->_type = Channel::TYPE_LUMINANCE;	chp->SetFormat( Channel::FORMAT_U8 ); ++chp;
		chp->_type = Channel::TYPE_ALPHA;		chp->SetFormat( Channel::FORMAT_U8 ); ++chp;
		break;
	case BF_R8G8B8:
		chp->_type = Channel::TYPE_RED;		chp->SetFormat( Channel::FORMAT_U8 ); ++chp;
		chp->_type = Channel::TYPE_GREEN;	chp->SetFormat( Channel::FORMAT_U8 ); ++chp;
		chp->_type = Channel::TYPE_BLUE;	chp->SetFormat( Channel::FORMAT_U8 ); ++chp;
		break;
	case BF_B8G8R8:
		chp->_type = Channel::TYPE_BLUE;	chp->SetFormat( Channel::FORMAT_U8 ); ++chp;
		chp->_type = Channel::TYPE_GREEN;	chp->SetFormat( Channel::FORMAT_U8 ); ++chp;
		chp->_type = Channel::TYPE_RED;		chp->SetFormat( Channel::FORMAT_U8 ); ++chp;
		break;

	case BF_R8G8B8A8:
		chp->_type = Channel::TYPE_RED;		chp->SetFormat( Channel::FORMAT_U8 ); ++chp;
		chp->_type = Channel::TYPE_GREEN;	chp->SetFormat( Channel::FORMAT_U8 ); ++chp;
		chp->_type = Channel::TYPE_BLUE;	chp->SetFormat( Channel::FORMAT_U8 ); ++chp;
		chp->_type = Channel::TYPE_ALPHA;	chp->SetFormat( Channel::FORMAT_U8 ); ++chp;
		break;
	case BF_B8G8R8A8:
		chp->_type = Channel::TYPE_BLUE;	chp->SetFormat( Channel::FORMAT_U8 ); ++chp;
		chp->_type = Channel::TYPE_GREEN;	chp->SetFormat( Channel::FORMAT_U8 ); ++chp;
		chp->_type = Channel::TYPE_RED;		chp->SetFormat( Channel::FORMAT_U8 ); ++chp;
		chp->_type = Channel::TYPE_ALPHA;	chp->SetFormat( Channel::FORMAT_U8 ); ++chp;
		break;
	case BF_B8G8R8X8:
		chp->_type = Channel::TYPE_BLUE;	chp->SetFormat( Channel::FORMAT_U8 ); ++chp;
		chp->_type = Channel::TYPE_GREEN;	chp->SetFormat( Channel::FORMAT_U8 ); ++chp;
		chp->_type = Channel::TYPE_RED;		chp->SetFormat( Channel::FORMAT_U8 ); ++chp;
		chp->_type = Channel::TYPE_EMPTY;	chp->SetFormat( Channel::FORMAT_U8 ); ++chp;
		break;

	case BF_R32FG32FB32F:
		chp->_type = Channel::TYPE_RED;		chp->SetFormat( Channel::FORMAT_F32 ); ++chp;
		chp->_type = Channel::TYPE_GREEN;	chp->SetFormat( Channel::FORMAT_F32 ); ++chp;
		chp->_type = Channel::TYPE_BLUE;	chp->SetFormat( Channel::FORMAT_F32 ); ++chp;
		break;

	default:
		PASSERT( 0 );
		break;
	}

	_base_format = base_format;
	_n_channels = chp - _channels;

	_pix_size = 0;
	for (int i=0; i < _n_channels; ++i)
	{
		_pix_size += _channel_format_sizes[ _channels[i].GetFormat() ];
	}

}

//==================================================================
ImageBase::ImageBase( ImageBase::BaseFormat base_format ) :
	_n_channels(0), _base_format(BF_NONE), _w(0), _h(0), _pitch(0), _pix_size(0)
{
	setBaseFormat( base_format );
}

//==================================================================
ImageBase::ImageBase( BaseFormat base_format, u_int w, u_int h, u_char *data_srcp, int pitch ) :
	_n_channels(0), _base_format(BF_NONE), _w(0), _h(0), _pitch(0), _pix_size(0)
{
	Setup( base_format, w, h, data_srcp, pitch );
}

//==================================================================
ImageBase::ImageBase() :
	_n_channels(0), _base_format(BF_NONE), _w(0), _h(0), _pitch(0), _pix_size(0)
{
}

//==================================================================
ImageBase::~ImageBase()
{
}

//==================================================================
void ImageBase::Setup( BaseFormat base_format, int w, int h, u_char *data_srcp, int pitch ) throw(...)
{
	for (int i=0; i < _n_channels; ++i)
		_channels[i].freeChannel();

	_n_channels = 0;

	setBaseFormat( base_format );

	int	tot_size = 0;
	int	offsets[32];

	for (int i=0; i < _n_channels; ++i)
		offsets[i] = 0;

	for (int i=0; i < _n_channels; ++i)
	{
		offsets[i] = tot_size;
		tot_size += _channel_format_sizes[ _channels[i].GetFormat() ];
	}

	_w = w;
	_h = h;

	if ( pitch )
		_pitch = pitch;
	else
		_pitch = _w * _pix_size;

	bool	owns_src_data = false;

	if NOT( data_srcp )
	{
		data_srcp = (u_char *)PSYS_CMALLOC( _pix_size * w * h );
		owns_src_data = true;
	}

	for (int i=0; i < _n_channels; ++i)
	{
		if ( i == 0 )
			_channels[i]._owns_data = owns_src_data;
		else
			_channels[i]._owns_data = false;

		_channels[i]._datap = data_srcp + offsets[i];
		_channels[i]._pitch = _pix_size;
	}
}

//==================================================================
int ImageBase::FindChannelByType( ImageBase::Channel::Type type ) const
{
	for (int i=0; i < _n_channels; ++i)
		if ( _channels[i]._type == type )
			return i;

	return -1;
}

//==================================================================
u_char ImageBase::GetChanU8( int chan_idx, int x, int y ) const
{
	PASSERT( (chan_idx >= 0 && chan_idx < _n_channels) &&
		     (x >= 0 && x < _w) &&
			 (y >= 0 && y < _h) );

	const ImageBase::Channel	* const chp = &_channels[chan_idx];

	return chp->_datap[ x * _pix_size + y * _pitch ];
}

//==================================================================
void ImageBase::SetChanU8( int chan_idx, int x, int y, u_char val )
{
	PASSERT( (chan_idx >= 0 && chan_idx < _n_channels) &&
		     (x >= 0 && x < _w) &&
			 (y >= 0 && y < _h) );

	ImageBase::Channel	*chp = &_channels[chan_idx];

	chp->_datap[ x * _pix_size + y * _pitch ] = val;
}

//==================================================================
const u_char *ImageBase::GetPixelPtr( int chan_idx, int x, int y ) const
{
	PASSERT( (chan_idx >= 0 && chan_idx < _n_channels) &&
		     (x >= 0 && x < _w) &&
			 (y >= 0 && y < _h) );

	const ImageBase::Channel	* const chp = &_channels[chan_idx];

	return &chp->_datap[ x * _pix_size + y * _pitch ];
}

//==================================================================
u_char *ImageBase::GetPixelPtr( int chan_idx, int x, int y )
{
	PASSERT( (chan_idx >= 0 && chan_idx < _n_channels) &&
		     (x >= 0 && x < _w) &&
			 (y >= 0 && y < _h) );

	ImageBase::Channel	*chp = &_channels[chan_idx];

	return &chp->_datap[ x * _pix_size + y * _pitch ];
}

//==================================================================
float ImageBase::GetChanF32( int chan_idx, int x, int y ) const
{
	PASSERT( (chan_idx >= 0 && chan_idx < _n_channels) &&
		     (x >= 0 && x < _w) &&
			 (y >= 0 && y < _h) );

	const ImageBase::Channel	* const chp = &_channels[chan_idx];

	return *(float *)&(chp->_datap[ x * _pix_size + y * _pitch ]);
}

//==================================================================
void ImageBase::SetChanF32( int chan_idx, int x, int y, float val )
{
	PASSERT( (chan_idx >= 0 && chan_idx < _n_channels) &&
		     (x >= 0 && x < _w) &&
			 (y >= 0 && y < _h) );

	ImageBase::Channel	*chp = &_channels[chan_idx];

	*(float *)&(chp->_datap[ x * _pix_size + y * _pitch ]) = val;
}

//==================================================================
void ImageBase::LoadAutodetect( const TCHAR *fnamep, LoadParams *paramsp )
{
	LoadParams loc_params;

	if NOT( paramsp )
		paramsp = &loc_params;

	paramsp->SetTypeFromFilename( fnamep );

	Load( Memfile(fnamep), paramsp );
}

//==================================================================
void ImageBase::Load( Memfile &mf, LoadParams *paramsp )
{
	if ERR_FALSE( paramsp != NULL )
		throw "Can't derive filetype";

	switch ( paramsp->_file_type )
	{
	case LoadParams::FT_BMP:
		LoadBMP( mf, paramsp );
		break;

	case LoadParams::FT_JPEG:
		LoadJPEG( mf, paramsp );
		break;

	case LoadParams::FT_UNKNWON:
		throw "Unknwon filetype";
	}
}

//==================================================================
void ImageBase::LoadBMP( Memfile &mf, LoadParams *paramsp )
{
	const u_char		*src_datap = (const u_char *)mf.GetData();
	const u_char		*src_datap2;
	myBITMAPFILEHEADER	bmfh;
	myBITMAPINFOHEADER	*bihp, tbih;
	int					clutsize;
	int					n_entries;

	src_datap2 = src_datap;
	memcpy( &bmfh, src_datap2, sizeof(bmfh) );
	PSYS_ENDIAN_FIX_L_WORD ( bmfh.bfType );
	PSYS_ENDIAN_FIX_L_DWORD( bmfh.bfSize );
	PSYS_ENDIAN_FIX_L_WORD ( bmfh.bfReserved1 );
	PSYS_ENDIAN_FIX_L_WORD ( bmfh.bfReserved2 );
	PSYS_ENDIAN_FIX_L_DWORD( bmfh.bfOffBits );
	src_datap2 += sizeof(bmfh);

	bihp = (myBITMAPINFOHEADER *)src_datap2;
	memcpy( &tbih, bihp, sizeof(myBITMAPINFOHEADER) );
	PSYS_ENDIAN_FIX_L_DWORD( tbih.biSize );
	PSYS_ENDIAN_FIX_L_LONG ( tbih.biWidth );
	PSYS_ENDIAN_FIX_L_LONG ( tbih.biHeight );
	PSYS_ENDIAN_FIX_L_WORD ( tbih.biPlanes );
	PSYS_ENDIAN_FIX_L_WORD ( tbih.biBitCount );
	PSYS_ENDIAN_FIX_L_DWORD( tbih.biCompression );
	PSYS_ENDIAN_FIX_L_DWORD( tbih.biSizeImage );
	PSYS_ENDIAN_FIX_L_LONG ( tbih.biXPelsPerMeter );
	PSYS_ENDIAN_FIX_L_LONG ( tbih.biYPelsPerMeter );
	PSYS_ENDIAN_FIX_L_DWORD( tbih.biClrUsed );
	PSYS_ENDIAN_FIX_L_DWORD( tbih.biClrImportant );
	src_datap2 += tbih.biSize;

	int dest_bits_per_pixel = 0;
	BaseFormat dest_format = BF_NONE;
	BaseFormat src_format = BF_NONE;
	switch ( tbih.biBitCount )
	{
	case 4:
		PASSERT( 0 );
		return;

	case 8:
		src_format = BF_INDEX8;

		n_entries = 256;
		clutsize = ( tbih.biClrUsed ? tbih.biClrUsed : (1 << tbih.biBitCount) );

		if ( paramsp && paramsp->_do_convert_truecolor )
		{
			dest_format = BF_R8G8B8;
		}
		else
		{
			dest_format = BF_INDEX8;
		}
		break;
/*
	case 16:
		clutsize = 0;
		src_format = BF_RGB565;
		dest_format = BF_RGB565;
		break;
*/
		PASSERT( 0 );
		return;

	case 24:
		clutsize = 0;
		src_format = BF_B8G8R8;
		dest_format = BF_R8G8B8;
		break;

	case 32:
		clutsize = 0;
		src_format = BF_B8G8R8A8;
		dest_format = BF_R8G8B8A8;
		break;

	default:
		throw "Bad Bitmap format";
		return;
	}

	if ( paramsp && paramsp->_do_force_alpha )
	{
		dest_format = BF_R8G8B8A8;
	}

	if ( paramsp && paramsp->_do_convert_alpha_luminance )
	{
		dest_format = BF_L8A8;
	}

	Setup( dest_format, tbih.biWidth, tbih.biHeight, NULL, 0 );

	// alloc and read bmap
	int src_pitch = ((tbih.biWidth * tbih.biBitCount / 8) + 3) & ~3;

	const u_char	*palettep = NULL;

	// read palette
	if ( clutsize )
	{
		palettep = src_datap2;
		src_datap2 += clutsize * sizeof(myRGBQUAD);
	}

	ImageBase	src_img( src_format, tbih.biWidth, tbih.biHeight, (u_char *)src_datap2, src_pitch );

	ImageBase	pal_img( BF_B8G8R8X8, 0, 256, (u_char *)palettep, 1 );
	ImageBase	*pal_imgp = NULL;
	if ( palettep )
		pal_imgp = &pal_img;

	bool	transp_detected = false;

	for (int y=0; y < _h; ++y)
	{
		bool	line_transp_detected;

		BlitFrom( src_img,
			0, _h - y - 1,
			_w, 1,
			0, y,
			&line_transp_detected,
			pal_imgp );

		transp_detected |= line_transp_detected;
	}

	if ( paramsp )
		paramsp->_out_transparency_detected = transp_detected;
}

//==================================================================
void ImageBase::BlitFrom( const ImageBase &src, int sx, int sy, int w, int h,
						  int dx, int dy,
						  bool *out_transp_detectedp,
						  ImageBase *src_palp )
{
	if ( out_transp_detectedp )
		*out_transp_detectedp = false;

	u_char	alpha_and = 255;

	/*if ( src._base_format == _base_format )
	{
		switch ( src._base_format )
		{
		case BF_R8G8B8A8:
		case BF_B8G8R8A8:
		case BF_A8L8:
		case BF_L8A8:
		case BF_L8:
			for (int i=0; i < h; ++i)
			{
				for (int j=0; j < w; ++j)
				{
					for (int c=0; c < src._n_channels; ++c)
					{
						u_char	val = src.GetChanU8( c, sx+j, sy+i );
						SetChanU8( c, dx+j, dy+i, val );

						if ( _channels[c]._type == Channel::TYPE_ALPHA )
							alpha_and &= val;
					}
				}
			}
			break;

		default:
			for (int i=0; i < h; ++i)
			{
				for (int j=0; j < w; ++j)
				{
					for (int c=0; c < src._n_channels; ++c)
					{
						u_char	val = src.GetChanU8( c, sx+j, sy+i );
						SetChanU8( c, dx+j, dy+i, val );
					}
				}
			}
		}
	}
	else*/
	if ( src._base_format == BF_INDEX8 )
	{
		if ( src_palp )
		{
			for (int i=0; i < h; ++i)
			{
				for (int j=0; j < w; ++j)
				{
					int	idx = src.GetChanU8( 0, sx+j, sy+i );

					for (int c=0; c < src_palp->_n_channels; ++c)
					{
						u_char	val = src_palp->GetChanU8( c, idx, 0 );
						SetChanU8( c, dx+j, dy+i, val );
					}
				}
			}
		}
		else
		{
			for (int i=0; i < h; ++i)
			{
				for (int j=0; j < w; ++j)
				{
					int	idx = src.GetChanU8( 0, sx+j, sy+i );

					for (int c=0; c < _n_channels; ++c)
					{
						SetChanU8( c, dx+j, dy+i, idx );

						if ( _channels[c]._type == Channel::TYPE_ALPHA )
							alpha_and &= idx;
					}
				}
			}
		}
	}
	else
	{
		int							src_chidx[ MAX_CHANNELS ];
		Channel::ConvertCallback	chan_conv_func[ MAX_CHANNELS ];
		for (int c=0; c < _n_channels; ++c)
		{
			Channel::Type	des_type = _channels[c]._type;

			src_chidx[c] = src.FindChannelByType( des_type );

			if ( src_chidx[c] < 0 )
			{
				chan_conv_func[c] = NULL;
			}
			else
			{
				chan_conv_func[c] = Channel::GetConverter( _channels[c].GetFormat(),
														   _channels[src_chidx[c]].GetFormat() );
			}
		}

		for (int i=0; i < h; ++i)
		{
			for (int j=0; j < w; ++j)
			{
				for (int c=0; c < src._n_channels; ++c)
				{
					if ( chan_conv_func[c] )
						chan_conv_func[c](	GetPixelPtr( c, j+dx, i+dy ),
											src.GetPixelPtr( src_chidx[c], sx+j, sy+i ) );

					//if ( _channels[c]._type == Channel::TYPE_ALPHA )
					//	alpha_and &= val;
				}
			}
		}
	}

	if ( alpha_and != 255 )
		if ( out_transp_detectedp )
			*out_transp_detectedp = true;
}

//==================================================================
void PUtils::ImageBase::LoadParams::SetTypeFromFilename( const TCHAR *fnamep )
{
	const TCHAR	*extp = PSYS::TStrGetFilenameExtension( fnamep );

	if ( !_tcsicmp( extp, _T("bmp") ) )
		_file_type = FT_BMP;
	else
	if ( !_tcsicmp( extp, _T("jpg") ) ||
		 !_tcsicmp( extp, _T("jpeg") ) )
		_file_type = FT_JPEG;
	else
		_file_type = FT_UNKNWON;
}

//==================================================================
bool PUtils::ImageBase::IsLoadableFilename_s( const TCHAR *fnamep )
{
	LoadParams	params;
	params.SetTypeFromFilename( fnamep );
	return ( params._file_type != LoadParams::FT_UNKNWON );
}

//==================================================================
void PUtils::ImageBase::CopyChannelFormats( const ImageBase &src )
{
	for (int i=0; i < src._n_channels; ++i)
	{
		_channels[i].freeChannel();

		_channels[i] = src._channels[i];
		_channels[i]._datap = NULL;
		_channels[i]._owns_data = false;
	}

	_n_channels = src._n_channels;
}

//==================================================================
void PUtils::ImageBase::ScaleQuarter()
{
	int	new_w = GetWidth()/2;
	int	new_h = GetHeight()/2;

	ImageBase	tmp_img( _base_format, new_w, new_h );

	for (int y=0; y < new_h; ++y)
	{
		for (int x=0; x < new_w; ++x)
		{
			int	xx = x * 2;
			int	yy = y * 2;

			for (int i=0; i < _n_channels; ++i)
			{
				Channel	*chanp = &_channels[i];
				Channel	*tmpchanp = &tmp_img._channels[i];

				u_char	*p00 = GetPixelPtr( i, xx+0, yy+0 );
				u_char	*p10 = GetPixelPtr( i, xx+1, yy+0 );
				u_char	*p01 = GetPixelPtr( i, xx+0, yy+1 );
				u_char	*p11 = GetPixelPtr( i, xx+1, yy+1 );

				float	val =
					chanp->GetAsF32( p00 ) +
					chanp->GetAsF32( p10 ) +
					chanp->GetAsF32( p01 ) +
					chanp->GetAsF32( p11 );

				tmpchanp->SetAsF32( tmp_img.GetPixelPtr( i, x, y ), val * 0.25f );
			}
		}
	}

	for (int i=0; i < _n_channels; ++i)
	{
		_channels[i].freeChannel();
	}

	*this = tmp_img;

	for (int i=0; i < _n_channels; ++i)
	{
		tmp_img._channels[i]._owns_data = false;
	}
}
